#include "Log/Log.h"

#include <algorithm>
#include <stdarg.h>
#include <string.h>

#include "Config/Config.h"
#include "Common/StringUtil.h"
#include "Net/UdpServer.h"
#include "Poco/DirectoryIterator.h"
#include "Poco/File.h"
#include "Poco/Net/SocketAddress.h"
#include "Poco/Path.h"
#include "UI.h"
#include "version.h"

using namespace std;
using namespace Poco;
using namespace Poco::Net;

Log zlog;
static FILE *g_pFile = NULL;
static int g_iTotalSize = 0;

#define LOG_SWARN_LEVEL "[SWarn] "
#define LOG_SINFO_LEVEL "[SInfo] "
#define LOG_SERRO_LEVEL "[SErro] "

#define LOG_UINFO_LEVEL "[UInfo] "
#define LOG_UERRO_LEVEL "[UErro] "

Log::Log() : BaseThread(true)
{
    m_iLogFileSizeLimit = 5 * 1024 * 1024;
    m_iLogFileCount = 2;
    m_strLogPath = "./log";
    m_bShowDate = m_bShowLevel = true;
}

void Log::InitFromConfig()
{
    try
    {
        //fileLog
        Poco::Dynamic::Var varFileLog = ConfigManager::Instance()->GetConfig(CONFIG_FILE)->Get("$.log.fileLog");
        SetLogFileSize((int)varFileLog["maxSize"]);
        m_iLogFileCount = (int)varFileLog["fileCount"];
        InitLogFile();

        //netLog
        Poco::Dynamic::Var varNetLog = ConfigManager::Instance()->GetConfig(CONFIG_FILE)->Get("$.log.netLog");
        for (size_t i = 0; i < varNetLog.size(); i++)
        {
            std::string strIP = varNetLog[i]["ip"].toString();
            if (StringUtil::IsIP(strIP) == false)
            {
                continue;
            }
            Poco::Mutex::ScopedLock lock(m_mutexUC);
            Poco::Net::DatagramSocket dataSocket;
            dataSocket.connect(SocketAddress(strIP, (int)varNetLog[i]["port"]));
            m_vectUdpClient.push_back(dataSocket);
        }

        //3rdLog
        Poco::Dynamic::Var var = ConfigManager::Instance()->GetConfig(CONFIG_FILE)->Get("$.log.3rdLog");
        for (Poco::Dynamic::Var::ConstIterator it = var.begin(); it != var.end(); ++it)
        {
            if ((*it)["enable"] == false)
            {
                continue;
            }

            LibLogProcessor* pLibLogProcessor = new LibLogProcessor((*it)["header"].toString());
            UdpServer* pUdpServer = new UdpServer((*it)["port"], pLibLogProcessor);
            pUdpServer->Start();
        }

        //setting
        m_bShowDate = ConfigManager::Instance()->GetConfig(CONFIG_FILE)->Get("$.log.showDate");
        m_bShowLevel = ConfigManager::Instance()->GetConfig(CONFIG_FILE)->Get("$.log.showLevel");

        //filters
        Poco::Dynamic::Var varFilters = ConfigManager::Instance()->GetConfig(CONFIG_FILE)->Get("$.log.filters");
        for (size_t i = 0; i < varFilters.size(); i++)
        {
            m_vectFilters.push_back(varFilters[i].toString());
        }
    }
    catch (...)
    {
        printf("Log::InitFromConfig catched\n");
    }
}

void Log::InitLogFile()
{
    Poco::File f(m_strLogPath);
    if (f.exists() == false)
    {
        f.createDirectory();
    }

    std::vector<std::string> vectLogFiles = GetAllLogFIles();
    if (vectLogFiles.empty())
    {
        g_pFile = OpenNewLogFile();
    }
    else
    {
        Poco::File flog(vectLogFiles[vectLogFiles.size() - 1]);
        if (flog.getSize() < m_iLogFileSizeLimit)
        {
            g_pFile = fopen(vectLogFiles[vectLogFiles.size() - 1].c_str(), "a+");
            g_iTotalSize = flog.getSize();
        }
        else
        {
            g_pFile = OpenNewLogFile();
            CheckLogFile();
        }
    }
}

void Log::SWarn(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    string strLog = VaList(LOG_SWARN_LEVEL, fmt, ap);
    va_end(ap);

    AddLog(strLog);
    SendUdp(strLog);
}

void Log::SInfo(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    string strLog = VaList(LOG_SINFO_LEVEL, fmt, ap);
    va_end(ap);

    AddLog(strLog);
    SendUdp(strLog);
}

void Log::SError(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    string strLog = VaList(LOG_SERRO_LEVEL, fmt, ap);
    va_end(ap);

    AddLog(strLog);
    SendUdp(strLog);
}

void Log::UInfo(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    string strLog = VaList(LOG_UINFO_LEVEL, fmt, ap);
    va_end(ap);

    AddLog(strLog);
    SendUdp(strLog);
}

void Log::UError(const char* fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    string strLog = VaList(LOG_UERRO_LEVEL, fmt, ap);
    va_end(ap);

    AddLog(strLog);
    SendUdp(strLog);
}

void Log::SetLogFileSize(int iBytes)
{
    m_iLogFileSizeLimit = iBytes;
}

void Log::SetLogFileCount(int iCount)
{
    m_iLogFileCount = iCount;
}

void Log::SetLogPath(std::string strPath)
{
    m_strLogPath = strPath;
}

int Log::SingleStep()
{
    std::vector<std::string> vectLog;
    if (1)
    {
        Poco::Mutex::ScopedLock lock(m_mutexLog);
        if (m_vectLog.empty())
        {
            return 10;
        }
        vectLog.swap(m_vectLog);
    }

    if (g_pFile == NULL)
    {
        return 1000;
    }
    for (std::vector<std::string>::iterator i = vectLog.begin(); i < vectLog.end(); ++i)
    {
        int iWrite = fwrite(i->c_str(), i->size(), 1, g_pFile);
        fflush(g_pFile);
        //printf("%s", i->c_str());
        if (iWrite > 0)
        {
            g_iTotalSize += i->size();
        }
        if (g_iTotalSize >= m_iLogFileSizeLimit)
        {
            fclose(g_pFile);
            g_pFile = OpenNewLogFile();
            g_iTotalSize = 0;
            if (g_pFile == NULL)
            {
                return 1000;
            }
            CheckLogFile();
        }
    }
    return NextLoop_UseEvent;
}

std::string Log::VaList(const char* pHead, const char* fmt, va_list& ap)
{
    char str[2048] = { 0 };
    int iIndex = 0;
    if (m_bShowDate)
    {
        std::string strDate = StringUtil::Date2String(StringUtil::DATE_FMT_LOG);
        memcpy(str + iIndex, strDate.c_str(), strDate.size());
        iIndex += strDate.size();
    }
    if (m_bShowLevel)
    {
        memcpy(str + iIndex, pHead, strlen(pHead));
        iIndex += strlen(pHead);
    }
    int len = vsnprintf(str + (size_t)iIndex, sizeof(str) - iIndex, fmt, ap);
    if (len >= 2048 - iIndex)
    {
        str[2046] = '\n';
        str[2047] = 0;
    }
    else
    {
        str[iIndex + len] = 0;
    }
    if (m_vectFilters.empty() == false)
    {
        bool bFind = false;
        std::string strLog = str;
        for (size_t i = 0; i < m_vectFilters.size(); i++)
        {
            if (strLog.find(m_vectFilters[i]) != string::npos)
            {
                bFind = true;
                break;
            }
        }
        if (bFind == false)
        {
            return "";
        }
    }
    return str;
}

void Log::AddLog(std::string &strLog)
{
    if (strLog.empty())
    {
        return;
    }
    printf("%s", strLog.c_str());
    if (1)
    {
        Poco::Mutex::ScopedLock lock(m_mutexLog);
        m_vectLog.push_back(strLog);
    }
    SetEvent();
}

void Log::SendUdp(std::string &strLog)
{
    if (strLog.empty())
    {
        return;
    }
    try
    {
        Poco::Mutex::ScopedLock lock(m_mutexUC);
        for (size_t i = 0; i < m_vectUdpClient.size(); i++)
        {
            m_vectUdpClient[i].sendBytes(strLog.c_str(), strLog.size());
        }
    }
    catch (...)
    {
        printf("Log::SendUdp catched\n");
    }
}

FILE* Log::OpenNewLogFile()
{
    string strFileName = m_strLogPath + "/" + StringUtil::Date2String(StringUtil::DATE_FMT_LOG_FILENAME) + ".log";
    FILE* fd = fopen(strFileName.c_str(), "w");
    if (fd == NULL)
    {
        return NULL;
    }
    else
    {
        return fd;
    }
}

void Log::CheckLogFile()
{
    try
    {
        std::vector<std::string> vectLogFiles = GetAllLogFIles();
        for (; vectLogFiles.size() > m_iLogFileCount;)
        {
            Poco::File fl(vectLogFiles[0]);
            vectLogFiles.erase(vectLogFiles.begin());
            fl.remove();
        }
    }
    catch (...)
    {
        ;
    }
}

std::vector<std::string> Log::GetAllLogFIles()
{
    try
    {
        std::vector<std::string> vectLogFiles;
        Poco::DirectoryIterator ie;
        for (Poco::DirectoryIterator i(m_strLogPath); i != ie; ++i)
        {
            vectLogFiles.push_back(m_strLogPath + "/" + i.name());
        }
        sort(vectLogFiles.begin(), vectLogFiles.end());
        return vectLogFiles;
    }
    catch (...)
    {
        return vector<string>();
    }
}
